using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using T.FAS.Runtime.Metadata.Entity.DataModel;
using T.FAS.Runtime.DataAccess.ShardingRule;
using T.FAS.Runtime.DataAccess.ShardingRuleManager;
using T.FAS.Runtime.Base.Entity;
using T.FAS.Runtime.DataAccess.Common;

namespace T.FAS.Runtime.DataAccess.Engine
{
    /// <summary>
    /// Sharding路由服务
    /// </summary>
    class ShardingRouteService
    {
        /// <summary>
        /// Sharding路由
        /// </summary>
        /// <param name="CompositeDataObject">领域模型</param>
        /// <param name="instance">数据</param>
        /// <param name="shardingKeyValue">ShardingValue</param>
        /// <returns>路由</returns>
        public Dictionary<string, ShardingTarget> Route(CompositeDataObject CompositeDataObject, object instance, ShardingValue shardingKeyValue = null)
        {
            var targets = new Dictionary<string, ShardingTarget>();

            if (shardingKeyValue == null)
            {
                if (CompositeDataObject.RootCompositeNodeObject.DataObject.IsTableSharding == false
                    && CompositeDataObject.RootCompositeNodeObject.DataObject.IsDatabaseSharding == false)
                {
                    foreach (var CompositeNodeObject in CompositeDataObject.CompositeNodeObjects)
                    {
                        var configkey = GetConfigkey(CompositeNodeObject);
                        targets.Add(CompositeNodeObject.DataObject.ID,
                            new ShardingTarget()
                            {
                                /*低代码改造CDO：DataSource切换为DatabaseID*/
                                DataSource = configkey,
                                TableName = CompositeNodeObject.DataObject.ActualTableNames ?? CompositeNodeObject.DataObject.LogicTableName
                            }); ;
                    }

                    return targets;
                }
                else
                {
                    shardingKeyValue = CreateShardingValue(CompositeDataObject, CompositeDataObject.RootCompositeNodeObject, instance);
                }
            }

            if (shardingKeyValue.ShardingValueType != ShardingValueType.SINGLE)
            {
                throw new FasException(DataAccessErrorCode.Sharding.Error, CommonStringClassEngine.ShardingRouteService_ObjectShardingRouteError+ shardingKeyValue.ShardingValueType);
            }

            foreach (var CompositeNodeObject in CompositeDataObject.CompositeNodeObjects)
            {
                var tars = ShardingRuleService.GetInstance().Parse(CompositeNodeObject.DataObject, shardingKeyValue);
                targets.Add(CompositeNodeObject.DataObject.ID, tars[0]);
            }

            return targets;
        }

        /// <summary>
        /// Sharding路由
        /// </summary>
        /// <param name="CompositeDataObject">领域模型</param>
        /// <param name="dataID">数据标识</param>
        /// <param name="shardingKeyValue">ShardingValue</param>
        /// <returns>路由</returns>
        public Dictionary<string, ShardingTarget> RouteByDataID(CompositeDataObject CompositeDataObject, string dataID, ShardingValue shardingKeyValue = null)
        {
            var targets = new Dictionary<string, ShardingTarget>();

            if (shardingKeyValue == null)
            {
                shardingKeyValue = CreateShardingValueByDataID(CompositeDataObject, CompositeDataObject.RootCompositeNodeObject, dataID);
            }

            //不分库分表
            if (shardingKeyValue == null)
            {
                foreach (var CompositeNodeObject in CompositeDataObject.CompositeNodeObjects)
                {
                    targets.Add(CompositeNodeObject.DataObject.ID,
                        new ShardingTarget()
                        {
                            //DataSource = CompositeNodeObject.DataObject.DataSourceName,
                            /*低代码改造CDO：DataSource切换为DatabaseID*/
                            DataSource = GetConfigkey(CompositeNodeObject),
                            TableName = CompositeNodeObject.DataObject.ActualTableNames?? CompositeNodeObject.DataObject.LogicTableName
                        });                    
                }
            }
            else
            {
                if (shardingKeyValue.ShardingValueType != ShardingValueType.SINGLE)
                {
                    throw new FasException(DataAccessErrorCode.Sharding.Error, CommonStringClassEngine.ShardingRouteService_ObjectShardingRouteError + shardingKeyValue.ShardingValueType);
                }

                foreach (var CompositeNodeObject in CompositeDataObject.CompositeNodeObjects)
                {
                    var tars = ShardingRuleService.GetInstance().Parse(CompositeNodeObject.DataObject, shardingKeyValue);
                    targets.Add(CompositeNodeObject.DataObject.ID, tars[0]);
                }
            }

            //Handle Association CompositeDataObject, Sharding is not support!
            foreach (var subCompositeNodeObject in CompositeDataObject.CompositeNodeObjects)
            {
                subCompositeNodeObject.Associations.ForEach(i =>
                {
                    if (i.AssociateType == AssociateType.OuterLeftJoin)
                    {
                        targets[i.AssoDataObjectID] =
                           new ShardingTarget()
                           {
                               //DataSource = i.AssoCompositeNodeObject.DataObject.DataSourceName,
                               /*低代码改造CDO：DataSource切换为DatabaseID*/
                               DataSource = GetConfigkey(i.AssoCompositeNodeObject),
                               TableName = i.AssoCompositeNodeObject.DataObject.ActualTableNames ?? i.AssoCompositeNodeObject.DataObject.LogicTableName
                           }
                           ;
                    }
                });
            }

            return targets;
        }

        /// <summary>
        /// Sharding路由
        /// </summary>
        /// <param name="CompositeDataObject">领域模型</param>        
        /// <param name="shardingKeyValue">ShardingValue</param>
        /// <returns>路由</returns>
        public Dictionary<string, ShardingTarget> RouteByShardingValue(CompositeDataObject CompositeDataObject, ShardingValue shardingKeyValue)
        {
            var targets = new Dictionary<string, ShardingTarget>();

            //不分库分表
            if (shardingKeyValue == null)
            {
                foreach (var CompositeNodeObject in CompositeDataObject.CompositeNodeObjects)
                {
                    targets[CompositeNodeObject.DataObject.ID]=
                        new ShardingTarget()
                        {
                            /*低代码改造CDO：DataSource切换为DatabaseID*/
                            //DataSource = CompositeNodeObject.DataObject.DataSourceName,
                            DataSource = GetConfigkey(CompositeNodeObject),
                            TableName = CompositeNodeObject.DataObject.ActualTableNames ?? CompositeNodeObject.DataObject.LogicTableName
                        };
                }
            }
            else
            {
                if (shardingKeyValue.ShardingValueType != ShardingValueType.SINGLE)
                {
                    throw new FasException(DataAccessErrorCode.Sharding.Error, CommonStringClassEngine.ShardingRouteService_ObjectShardingRouteError + shardingKeyValue.ShardingValueType);
                }

                foreach (var CompositeNodeObject in CompositeDataObject.CompositeNodeObjects)
                {
                    var tars = ShardingRuleService.GetInstance().Parse(CompositeNodeObject.DataObject, shardingKeyValue);
                    targets.Add(CompositeNodeObject.DataObject.ID, tars[0]);
                }
            }

            //Handle Association CompositeDataObject, Sharding is not support!
            foreach (var subCompositeNodeObject in CompositeDataObject.CompositeNodeObjects)
            {
                subCompositeNodeObject.Associations.ForEach(i =>
                {
                    if (i.AssociateType == AssociateType.OuterLeftJoin)
                    { 
                        targets[i.AssoDataObjectID] =
                           new ShardingTarget()
                           {
                               //DataSource = i.AssoCompositeNodeObject.DataObject.DataSourceName,
                               /*低代码改造CDO：DataSource切换为DatabaseID*/
                               DataSource = GetConfigkey(i.AssoCompositeNodeObject),
                               TableName = i.AssoCompositeNodeObject.DataObject.ActualTableNames ?? i.AssoCompositeNodeObject.DataObject.LogicTableName
                           }
                           ;
                    }
                });
            }

            return targets;
        }

        /// <summary>
        /// Sharding路由
        /// </summary>
        /// <param name="CompositeDataObject">领域模型</param>
        /// <param name="instance">数据</param>
        /// <param name="shardingKeyValue">ShardingValue</param>
        /// <returns>路由</returns>
        public Dictionary<string, ShardingTarget> Route(CompositeDataObject CompositeDataObject, CompositeNodeObject CompositeNodeObject, object instance, ShardingValue shardingKeyValue = null)
        {
            var targets = new Dictionary<string, ShardingTarget>();

            if (shardingKeyValue == null)
            {
                if (CompositeDataObject.RootCompositeNodeObject.DataObject.IsTableSharding == false
                   && CompositeDataObject.RootCompositeNodeObject.DataObject.IsDatabaseSharding == false)
                {
                    foreach (var domainObj in CompositeDataObject.CompositeNodeObjects)
                    {
                        targets.Add(domainObj.DataObject.ID,
                            new ShardingTarget()
                            {
                                /*低代码改造CDO：DataSource切换为DatabaseID*/
                                DataSource = GetConfigkey(domainObj),
                                //DataSource = domainObj.DataObject.DataSourceName,
                                TableName = domainObj.DataObject.ActualTableNames ?? domainObj.DataObject.LogicTableName
                            });
                    }

                    return targets;
                }
                else
                {
                    shardingKeyValue = CreateShardingValue(CompositeDataObject, CompositeNodeObject, instance);
                }
            }

            if (shardingKeyValue.ShardingValueType != ShardingValueType.SINGLE)
            {
                throw new FasException(DataAccessErrorCode.Sharding.Error, CommonStringClassEngine.ShardingRouteService_ObjectShardingRouteError + shardingKeyValue.ShardingValueType);
            }

            var tars = ShardingRuleService.GetInstance().Parse(CompositeNodeObject.DataObject, shardingKeyValue);
            targets.Add(CompositeNodeObject.DataObject.ID, tars[0]);

            //Handle Association CompositeDataObject, Sharding is not support!
            foreach (var subCompositeNodeObject in CompositeDataObject.CompositeNodeObjects)
            {
                subCompositeNodeObject.Associations.ForEach(i =>
                {
                    if (i.AssociateType == AssociateType.OuterLeftJoin)
                    {
                        targets[i.AssoDataObjectID] =
                           new ShardingTarget()
                           {
                               //DataSource = i.AssoCompositeNodeObject.DataObject.DataSourceName,
                               /*低代码改造CDO：DataSource切换为DatabaseID*/
                               DataSource = GetConfigkey(i.AssoCompositeNodeObject),
                               TableName = i.AssoCompositeNodeObject.DataObject.ActualTableNames ?? i.AssoCompositeNodeObject.DataObject.LogicTableName
                           }
                           ;
                    }
                });
            }

            return targets;
        }

        /// <summary>
        /// 构造ShardingValue
        /// </summary>
        /// <param name="CompositeDataObject">领域模型</param>
        /// <param name="instance">数据</param>
        /// <returns>ShardingValue</returns>
        private ShardingValue CreateShardingValue(CompositeDataObject CompositeDataObject, CompositeNodeObject CompositeNodeObject, object instance)
        {
            ShardingValue shardingValue = null;
            var shardingColumn = CompositeNodeObject.DataObject.Columns.FirstOrDefault(i => i.IsShardingColumn);
            if (shardingColumn == null)
            {
                throw new FasException(DataAccessErrorCode.Sharding.NullValue, CommonStringClassEngine.ShardingRouteService_CreateShardingValue+ CompositeNodeObject.DataObjectID);
            }

            var shardingElement = CompositeNodeObject.Elements.FirstOrDefault(i => i.DataColumnID == shardingColumn.ID);

            var propValue = ObjectPropertyValueUtils.GetPropValue(shardingElement.PropertyName, instance);
            switch (shardingElement.DataType)
            {
                case DataType.DateTime:
                case DataType.Date:
                    shardingValue = new ShardingValue(CompositeDataObject.Name, shardingElement.ID, Convert.ToDateTime(propValue));
                    break;
                case DataType.Int:
                    shardingValue = new ShardingValue(CompositeDataObject.Name, shardingElement.ID, Convert.ToInt64(propValue));
                    break;
                case DataType.String:
                default:
                    shardingValue = new ShardingValue(CompositeDataObject.Name, shardingElement.ID, Convert.ToString(propValue));
                    break;
            }

            return shardingValue;
        }

        /// <summary>
        /// 构造ShardingValue
        /// </summary>
        /// <param name="CompositeDataObject">领域模型</param>
        /// <param name="instance">数据</param>
        /// <returns>ShardingValue</returns>
        private ShardingValue CreateShardingValueByDataID(CompositeDataObject CompositeDataObject, CompositeNodeObject CompositeNodeObject, string dataID)
        {
            ShardingValue shardingValue = null;
            var shardingColumn = CompositeNodeObject.DataObject.Columns.FirstOrDefault(i => i.IsShardingColumn);
            if (shardingColumn == null)
            {
                return shardingValue;
            }

            var shardingElement = CompositeNodeObject.Elements.FirstOrDefault(i => i.DataColumnID == shardingColumn.ID);
            switch (shardingElement.DataType)
            {
                case DataType.DateTime:
                case DataType.Date:
                    shardingValue = new ShardingValue(CompositeDataObject.Name, shardingElement.ID, Convert.ToDateTime(dataID));
                    break;
                case DataType.Int:
                    shardingValue = new ShardingValue(CompositeDataObject.Name, shardingElement.ID, Convert.ToInt64(dataID));
                    break;
                case DataType.String:
                default:
                    shardingValue = new ShardingValue(CompositeDataObject.Name, shardingElement.ID, Convert.ToString(dataID));
                    break;
            }

            return shardingValue;
        }

        private string GetConfigkey(CompositeNodeObject CompositeNodeObject)
        {
            var configkey = CompositeNodeObject.DataObject?.Database.DatabaseConfigs.Where(cfg => cfg.IsDefault).FirstOrDefault()?.ConfigKey;
            if (string.IsNullOrEmpty(configkey))
            {
                throw new FasException(DataAccessErrorCode.Sharding.Error, string.Format(CommonStringClassEngine.ShardingRouteService_GetConfigkey,CompositeNodeObject.CompositeDataObjectID)+
string.Format(CommonStringClassEngine.ShardingRouteService_GetConfigkey1,CompositeNodeObject.ID)+
string.Format(CommonStringClassEngine.ShardingRouteService_GetConfigkey2,CompositeNodeObject.DataObject?.Code)+
string.Format(CommonStringClassEngine.ShardingRouteService_GetConfigkey3,CompositeNodeObject.DataObject?.Database?.Code));
            }
            return configkey;
        }

        private string GetConfigkey(DataObject dataobject)
        {
            var configkey = dataobject?.Database.DatabaseConfigs.Where(cfg => cfg.IsDefault).FirstOrDefault()?.ConfigKey;
            if (string.IsNullOrEmpty(configkey))
            {
                throw new FasException(DataAccessErrorCode.Sharding.Error, string.Format(CommonStringClassEngine.ShardingRouteService_GetConfigkey4,dataobject?.Code)+
string.Format(CommonStringClassEngine.ShardingRouteService_GetConfigkey5,dataobject?.Database?.Code));
            }
            return configkey;
        }
    }
}
